package analyzeLogFiles;

import com.maxmind.geoip.*;
import java.io.IOException;

import database.*;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.StringTokenizer;
import analyzeLogFiles.ConnectionObject;


/**
 * AnalyzeLogLine class takes a log string generated from a JHAVE server
 *		and extracts information about the user, visualizations, and IP to
 *		send to a database.
 * @author Gabrielle DuBois
 *
 */
public class AnalyzeLogLine {
	private String databaseFile = "./lib/GeoLiteCity.dat";
	private ArrayList<ConnectionObject> activeConnections = new ArrayList<ConnectionObject>();
	private ArrayList<String[]> waitingForVisualName = new ArrayList<String[]>();

	//a new connection log line includes UID and IP address
	private final String foundNewConnection = "Accepted connection";
	private final String foundVisualUsed = "FINE: http://";
	private final String foundVisualName = "sendtoClient - 400";


	//not going to use for now. assuming that reset the servers often enough...
	//otherwise will never be able to take lines out of order....
	//private final String foundUserKilled = "Killing client ";

	private final String uidRegex = "[0-9]*";
	// X{n,m}? 	X, at least n but not more than m times
	// X{n}? 	X, exactly n times
	private final String ipRegex = "[0-9]{1,3}?.[0-9]{1,3}?.[0-9]{1,3}?.[0-9]{1,3}?";
	private final String dateRegex = "[0-9]{1,2}?/[0-9]{1,2}?/[0-9]{2,4}?";
	//private final String visualRegex = the string directly after the 400;

	/**
 	  * Constructor
 	  */
    public AnalyzeLogLine(){ }//constructor

	/**
 	  * Parses a string for information to send to database.
 	  * @param server a String containing the name of the server sending the
 	  			the information.
 	  * @param line a String containing a single log line.
 	  * @return A boolean expressing if the log line causes anything new to be
 	  *				added or updated.
 	  */
    public boolean acceptLogLine(String server, String line){
		boolean updatedSomething = true;
		StringTokenizer stok;

		// if you see a uid check activeConnection arrayList. if it doesn't exist add it
		if (line.contains(foundNewConnection)){
			String uid = null;
			String ip = null;
			int dbid;
			boolean addToActiveConnectionArray = false;
			ConnectionObject connObj = null;
			DAOfactory mysqlFactory = DAOfactory.getDAOfactory(DAOfactory.MYSQL);
			ConnectionDAO connDAO = mysqlFactory.getConnectionDAO();
			UsedDAO usedDAO = mysqlFactory.getUsedDAO();
			stok = new StringTokenizer(line);
			String temp;

			//grab uid and ip
			int numTokens = stok.countTokens();
			for (int i=0; i < numTokens; i++){
				temp = stok.nextToken();

				//System.out.println("Temp Value: " + temp);
				if ( Pattern.matches(uidRegex, temp) ){
					uid = temp;
					//System.out.println("uid: "+uid);
				}
				if ( Pattern.matches(ipRegex, temp) ){
					ip = temp;
					//System.out.println("ip: "+ip);
				}
			}//for(i)

			//check to see if uid has already been seen
			for (int i=0; i<activeConnections.size(); i++){
				if ( uid.equals(activeConnections.get(i).getUserID()) &&
						server.equals(activeConnections.get(i).getServerName()) ){
					connObj = activeConnections.get(i);
					addToActiveConnectionArray = false;
					break;
				}
			}//for(i)

			// if uid hasn't been seen make a new ConnectionObject
			if (connObj == null){
				connObj = new ConnectionObject(server,uid);
				addToActiveConnectionArray = true;
			}

			//get info about ip address then write out to database saving dbID
			String[] ipInfo = ipLookup(ip);
			dbid = connDAO.addConnection(server, ip, "not.implemented@sorry.com", ipInfo[0], ipInfo[1], ipInfo[2]);
			connObj.setDatabaseID(dbid);

			//now that we've seen the ip address write out any stored time/visual info
			ArrayList<String> timeVisuals = connObj.setSawIP(true);
			for (int i=0; i<timeVisuals.size(); i+=2){
				usedDAO.addUsage( dbid, timeVisuals.get(i), timeVisuals.get(i+1) );
			}//for(i)

			if (addToActiveConnectionArray)
				activeConnections.add(connObj);
			System.out.println("Found New Connection = ServerName: "+server+" UID: "+uid+" IP: "+ip+" dbID: "+dbid);

		}//if
		else if(line.contains(foundVisualUsed)){
			// check to see if uid is in activeConnection. if not add it!
			String uid = null;
			stok = new StringTokenizer(line, " /.\t\n\r\f");
			String temp;

			//grab uid
			int numTokens = stok.countTokens();
			for (int i=0; i < numTokens; i++){
				temp = stok.nextToken();

				if ( Pattern.matches(uidRegex, temp) ){
					uid = temp;
				}
			}//for(i)

			//store server name and uid in waitingForVisualName ArrayList
			String[] serverUID = {server, uid};
			waitingForVisualName.add(serverUID);

			boolean alreadyInArray = false;
			for (int j=0; j<activeConnections.size(); j++){
				if ( uid.equals(activeConnections.get(j).getUserID()) &&
						server.equals(activeConnections.get(j).getServerName()) ){
					alreadyInArray = true;
					break;
				}
			}//for(j)

			if (!alreadyInArray){
				activeConnections.add( new ConnectionObject(server, uid) );
			}

			System.out.println("Found Visualization Used = ServerName: "+server+" UID: "+uid);
		}
		else if(line.contains(foundVisualName)){
			// if its a visualization usage check to see if its been written to db (if the IPAddress has been seen)
			// if it hasn't save it, if it has then write it out to db as well
			String[] serverUID;
			String visualName = null;
			String dateStamp = null;
			DAOfactory mysqlFactory = DAOfactory.getDAOfactory(DAOfactory.MYSQL);
			UsedDAO usedDAO = mysqlFactory.getUsedDAO();
			stok = new StringTokenizer(line);
			String temp;

			//grab visualName. string right after the 400
			int numTokens = stok.countTokens();
			for (int i=0; i < numTokens; i++){
				temp = stok.nextToken();

				if ( Pattern.matches(dateRegex, temp) ){
					dateStamp = temp;
				}
				if ( temp.equals("400") ){
					visualName = stok.nextToken();
					System.out.println("visualName: "+ visualName);
					break;
				}
			}//for(i)

			for (int i=0; i<waitingForVisualName.size(); i++){
				serverUID = waitingForVisualName.get(i);
				if ( server.equals(serverUID[0]) ){
					// add to database and remove from waiting list
					String uidTemp = serverUID[1];
					String serverTemp = serverUID[0];
					int dbidTemp;

					for (int j=0; j<activeConnections.size(); j++){
						if ( uidTemp.equals(activeConnections.get(j).getUserID()) &&
								serverTemp.equals(activeConnections.get(j).getServerName()) ){
							dbidTemp = activeConnections.get(j).getDatabaseID();

							usedDAO.addUsage( dbidTemp, dateStamp, visualName );
							System.out.println("Found Visualization Name = ServerName: "+serverTemp+" UID: "+uidTemp+" DBID: "+dbidTemp+" VisualName: "+visualName);
							break;
						}
					}//for(j)

					waitingForVisualName.remove(i);
					break;
				}
			}//for(i)
		}
		else
			updatedSomething = false;

		return updatedSomething;
    }//acceptLogLine()


	/**
 	  * Internal method to look up and retrieve information about the ip address
	  * @param IPAddress is a String containing representing the IP address
	  * 		needed to be looked up
	  * @return a String[] containing first the value of the country, then
	  * 		the value of the region and lastly the value of the city
 	  */
	public String[] ipLookup(String IPAddress){
		String[] countryRegionCity = new String[3];

		try {
			LookupService cl = new LookupService(databaseFile, LookupService.GEOIP_MEMORY_CACHE );
			Location loc = cl.getLocation(IPAddress);

			countryRegionCity[0] = loc.countryName;
			countryRegionCity[1] = regionName.regionNameByCode(loc.countryCode, loc.region);
			countryRegionCity[2] = loc.city;

			cl.close();
		}
		catch (IOException e) {
			System.out.println("Error in AnalyzeLogLine.ipLookup(): "+e);
		}

		return countryRegionCity;
	}//ipLookup()


	/**
 	  * Resets all the internal collection of connections. Call after each
 	  * time the user ids have been reset (either because the JHAVE servers have
 	  * all been reset or because done working with a group of tar balls.
	  *
 	  * @return A boolean expressing if the information being stored was reset.
 	  */
    public boolean resetAllConnections(){
		activeConnections = new ArrayList<ConnectionObject>();

		return true;
    }//resetAllConnections()


    /**
 	  * Resets all the internal collection of connections for one server.
 	  * @param serverName is a String which contains the name of the server
 	  * 		who's connection information needs to be reset.
 	  * @return A boolean expressing if the information being stored was reset.
 	  */
    public boolean resetServerConnections(String serverName){
    	ConnectionObject oc;

		for (int i=0; i<activeConnections.size(); i++){
			oc = activeConnections.get(i);

			if (oc.getServerName().equals(serverName))
				activeConnections.remove(i);
		}//for(i)


		return true;
    }//resetServerConnections()


}//AnalyzeLogLine
